package com.yzd.learnjava.concurrency;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

import com.yzd.learnjava.enumerated.Course;
import com.yzd.learnjava.enumerated.Food;
import com.yzd.learnjava.util.Print;

public class RestaurantWithQueues {
	public static void main(String[] args) throws IOException {
		ExecutorService exec = Executors.newCachedThreadPool();
		Restaurant2 restaurant = new Restaurant2(exec, 5, 2);
		exec.execute(restaurant);
		
		Print.print("Press 'Enter' to quit");
		System.in.read();
		exec.shutdownNow();
	}
}
class Order2{
	private static int counter = 0;
	private final int id = counter++;
	private final Customer2 customer;
	private final WaitPerson2 waitPerson;
	private final Food food;
	public Order2(Customer2 cust, WaitPerson2 wp, Food f ) {
		this.customer = cust;
		this.waitPerson = wp;
		this.food = f;
	}
	
	public Food item() {
		return food;
	}
	
	public Customer2 getCustomer() {
		return customer;
	}
	
	public WaitPerson2 getWaitPerson() {
		return waitPerson;
	}
	public String toString() {
		return "Order: " + id + " item:" + food + " for:" + customer + " served by " + waitPerson;
	}
	
}

class Plate2{
	private final Order2 order;
	private final Food food ;
	public Plate2(Order2 order, Food f ) {
		this.order = order;
		this.food = f;
	}
	public Order2 getOrder() {
		return order;
	}
	public Food getFood() {
		return food;
	}
	public String toString() {
		return food.toString();
	}
}

class Customer2 implements Runnable{
	private static int counter = 0;
	private final int id = counter++;
	private final WaitPerson2 waitPerson;
	private SynchronousQueue<Plate2> placeSetting = new SynchronousQueue<Plate2>();
	public Customer2(WaitPerson2 w) {
		this.waitPerson = w ;
	}
	public void deliver(Plate2 plate) throws InterruptedException {
		placeSetting.put(plate);
	}
	public void run() {
		for(Course course : Course.values()) {
			Food food = course.randomSelection();
			try {
				waitPerson.placeOrder(this, food);
				Print.print(this + "eating " + placeSetting.take());
			} catch (InterruptedException e) {
				Print.print(this + "waiting for " + course + " interrupted");
				e.printStackTrace();
			}
		}
		Print.print(this + "finished meal , leaving");
	}
	
	public String toString() {
		return "Customer " + id + " ";
	}
}

class WaitPerson2 implements Runnable{
	private static int counter = 0;
	private final int id = counter++;
	private final Restaurant2 restaurant;
	BlockingQueue<Plate2> filledOrders = new LinkedBlockingQueue<Plate2>();
	public WaitPerson2(Restaurant2 rest) {
		this.restaurant = rest;
	}
	public void placeOrder(Customer2 cust, Food food) {
		try {
			restaurant.orders.put(new Order2(cust, this, food));
		} catch (InterruptedException e) {
			Print.print(this + " placeOrder interrupted");
			e.printStackTrace();
		}
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				Plate2 plate = filledOrders.take();
				Print.print(this + "received " + plate + " delivering to " + plate.getOrder().getCustomer());
				plate.getOrder().getCustomer().deliver(plate);
			}
		} catch (InterruptedException e) {
			Print.print(this + " interrupted");
			e.printStackTrace();
		}
		Print.print(this + " off duty");
	}
	public String toString() {
		return "WaitPerson " + id + " ";
	}
}

class Chef2 implements Runnable{
	private static int counter = 0;
	private final int id = counter++;
	private final Restaurant2 restaurant;
	private static Random random = new Random(47);
	public Chef2(Restaurant2 rest) {
		this.restaurant = rest;
	}
	
	public void run() {
		try {
			while(!Thread.interrupted()) {
				Order2 order = restaurant.orders.take();
				Food requestedItem = order.item();
				TimeUnit.MILLISECONDS.sleep(random.nextInt(500));
				Plate2 plate = new Plate2(order, requestedItem);
				order.getWaitPerson().filledOrders.put(plate);
			}
		} catch (InterruptedException e) {
			Print.print(this + " interuppted");
			e.printStackTrace();
		}
		Print.print(this + " off duty");
	}
}

class Restaurant2 implements Runnable{
	private List<WaitPerson2> waitPersons = new ArrayList<WaitPerson2>();
	private List<Chef2> chefs = new ArrayList<Chef2>();
	private ExecutorService exec ;
	private static Random random = new Random(47);
	BlockingQueue<Order2> orders = new LinkedBlockingQueue<Order2>();
	
	public Restaurant2(ExecutorService ex, int nWaitPersons, int nChefs) {
		this.exec = ex ;
		for(int i = 0 ; i < nWaitPersons; i++) {
			WaitPerson2 wp = new WaitPerson2(this);
			waitPersons.add(wp);
			exec.execute(wp);
		}
		for(int i = 0 ; i < nChefs; i++) {
			Chef2 chef = new Chef2(this);
			chefs.add(chef);
			exec.execute(chef);
		}
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				WaitPerson2 wp = waitPersons.get(random.nextInt(waitPersons.size()));
				Customer2 c = new Customer2(wp);
				exec.execute(c);
				TimeUnit.MILLISECONDS.sleep(100);
			}
		} catch (InterruptedException e) {
			Print.print("Restaurant interrupted");
			e.printStackTrace();
		}
		Print.print("Restaurant closing");
	}
}
































